1 # Blackbox logging internals
3 The Blackbox is designed to record the raw internal state of the flight controller at near-maximum rate. By logging the
4 raw inputs and outputs of key flight systems, the Blackbox log aims to allow the offline bench-top simulation, debugging,
5 and testing of flight control algorithms using data collected from real flights.
7 A typical logging regime might capture 30 different state variables (for an average of 28 bytes per frame) at a sample
8 rate of 900Hz. That's about 25,000 bytes per second, which is 250,000 baud with a typical 8-N-1 serial encoding.
12 Please refer to the source code to clarify anything this document leaves unclear:
14 * Cleanflight's Blackbox logger: [blackbox.c](https://github.com/cleanflight/cleanflight/blob/master/src/main/blackbox/blackbox.c),
15 [blackbox_io.c](https://github.com/cleanflight/cleanflight/blob/master/src/main/blackbox/blackbox_io.c),
16 [blackbox_fielddefs.h](https://github.com/cleanflight/cleanflight/blob/master/src/main/blackbox/blackbox_fielddefs.h)
17 * [C implementation of the Blackbox log decoder](https://github.com/cleanflight/blackbox-tools/blob/master/src/parser.c)
18 * [JavaScript implementation of the Blackbox log decoder](https://github.com/cleanflight/blackbox-log-viewer/blob/master/js/flightlog_parser.js)
21 Blackbox is designed for flight controllers that are based around the concept of a "main loop". During each main loop
22 iteration, the flight controller will read some state from sensors, run some flight control algorithms, and produce some
23 outputs. For each of these loop iterations, a Blackbox "logging iteration" will be executed. This will read data that
24 was stored during the execution of the main loop and log this data to an attached logging device. The data will include
25 algorithm inputs such as sensor and RC data, intermediate results from flight control algorithms, and algorithm outputs
26 such as motor commands.
29 Each event which is recorded to the log is packaged as a "log frame". Blackbox only uses a handful of different types of
30 log frames. Each frame type is identified by a single capital letter.
33 The most basic kind of logged frames are the "main frames". These record the primary state of the flight controller (RC
34 input, gyroscopes, flight control algorithm intermediates, motor outputs), and are logged during every logging
37 Each main frame must contain at least two fields, "loopIteration" which records the index of the current main loop
38 iteration (starting at zero for the first logged iteration), and "time" which records the timestamp of the beginning of
39 the main loop in microseconds (this needn't start at zero, on Cleanflight it represents the system uptime).
41 There are two kinds of main frames, "I" and "P". "I", or "intra" frames are like video keyframes. They can be decoded
42 without reference to any previous frame, so they allow log decoding to be resynchronized in the event of log damage. "P"
43 or "inter" frames use an encoding which references previously logged frames in order to reduce the required datarate.
44 When one interframe fails to decode, all following interframes will be undecodable up until the next intraframe.
47 Because the GPS is updated so infrequently, GPS data is logged in its own dedicated frames. These are recorded whenever
48 the GPS data changes (not necessarily alongside every main frame). Like the main frames, the GPS frames have their own
49 intra/inter encoding system.
51 The "H" or "home" frame records the lat/lon of a reference point. The "G" or "GPS" frame records the current state of
52 the GPS system (current position, altitude etc.) based on the reference point. The reference point can be updated
53 (infrequently) during the flight, and is logged whenever it changes.
55 To allow "G" frames to continue be decoded in the event that an "H" update is dropped from the log, the "H" frame is
56 logged periodically even if it has not changed (say, every 10 seconds). This caps the duration of unreadble "G" frames
57 that will result from a single missed "H" change.
60 Some flight controller state is updated very infrequently (on the order of once or twice a minute). Logging the fact
61 that this data had not been updated during every single logging iteration would be a waste of bandwidth, so these frames
62 are only logged when the "slow" state actually changes.
64 All Slow frames are logged as intraframes. An interframe encoding scheme can't be used for Slow frames, because one
65 damaged frame causes all subsequent interframes to be undecodable. Because Slow frames are written so infrequently, one
66 missing Slow frame could invalidate minutes worth of Slow state.
68 On Cleanflight, Slow frames are currently used to log data like the user-chosen flight mode and the current failsafe
72 Some flight controller data is updated so infrequently or exists so transiently that we do not log it as a flight
73 controller "state". Instead, we log it as a state *transition* . This data is logged in "E" or "event" frames. Each event
74 frame payload begins with a single byte "event type" field. The format of the rest of the payload is not encoded in the
75 flight log, so its interpretation is left up to an agreement of the writer and the decoder.
77 For example, one event that Cleanflight logs is that the user has adjusted a system setting (such as a PID setting)
78 using Cleanflight's inflight adjustments feature. The event payload notes which setting was adjusted and the new value
81 Because these setting updates are so rare, it would be wasteful to treat the settings as "state" and log the fact that
82 the setting had not been changed during every logging iteration. It would be infeasible to periodically log the system
83 settings using an intra/interframe scheme, because the intraframes would be so large. Instead we only log the
84 transitions as events, accept the small probability that any one of those events will be damaged/absent in the log, and
85 leave it up to log readers to decide the extent to which they are willing to assume that the state of the setting
86 between successfully-decoded transition events was truly unchanged.
89 For every field in a given frame type, there is an associated name, predictor, and encoding.
91 When a field is written, the chosen predictor is computed for the field, then this predictor value is subtracted from
92 the raw field value. Finally, the encoder is used to transform the value into bytes to be written to the logging device.
95 The job of the predictor is to bring the value to be encoded as close to zero as possible. The predictor may be based
96 on the values seen for the field in a previous frame, or some other value such as a fixed value or a value recorded in
97 the log headers. For example, the battery voltage values in "I" intraframes in Cleanflight use a reference voltage that
98 is logged as part of the headers as a predictor. This assumes that battery voltages will be broadly similar to the
99 initial pack voltage of the flight (e.g. 4S battery voltages are likely to lie within a small range for the whole
100 flight). In "P" interframes, the battery voltage will instead use the previously-logged voltage as a predictor, because
101 the correlation between successive voltage readings is high.
103 These predictors are presently available:
105 #### Predict zero (0)
106 This predictor is the null-predictor which doesn't modify the field value at all. It is a common choice for fields
107 which are already close to zero, or where no better history is available (e.g. in intraframes which may not rely on the
108 previous value of fields).
110 #### Predict last value (1)
111 This is the most common predictor in interframes. The last-logged value of the field will be used as the predictor, and
112 subtracted from the raw field value. For fields which don't change very often, this will make their encoded value be
113 normally zero. Most fields have some time-correlation, so this predictor should reduce the magnitude of all but the
116 #### Predict straight line (2)
117 This predictor assumes that the slope between the current measurement and the previous one will be similar to the
118 slope between the previous measurement and the one before that. This is common for fields which increase at a steady rate,
119 such as the "time" field. The predictor is `history_age_2 - 2 * history_age_1`.
121 #### Predict average 2 (3)
122 This predictor is the average of the two previously logged values of the field (i.e. `(history_age_1 + history_age_2) / 2`
123 ). It is used when there is significant random noise involved in the field, which means that the average of the recent
124 history is a better predictor of the next value than the previous value on its own would be (for example, in gyroscope
125 or motor measurements).
127 #### Predict minthrottle (4)
128 This predictor subtracts the value of "minthrottle" which is included in the log header. In Cleanflight, motors always
129 lie in the range of `[minthrottle ... maxthrottle]` when the craft is armed, so this predictor is used for the first
130 motor value in intraframes.
132 #### Predict motor[0] (5)
133 This predictor is set to the value of `motor[0]` which was decoded earlier within the current frame. It is used in
134 intraframes for every motor after the first one, because the motor commands typically lie in a tight grouping.
136 #### Predict increment (6)
137 This predictor assumes that the field will be incremented by 1 unit for every main loop iteration. This is used to
138 predict the `loopIteration` field, which increases by 1 for every loop iteration.
140 #### Predict home-coord (7)
141 This predictor is set to the corresponding latitude or longitude field from the GPS home coordinate (which was logged in
142 a preceding "H" frame). If no preceding "H" frame exists, the value is marked as invalid.
144 #### Predict 1500 (8)
145 This predictor is set to a fixed value of 1500. It is preferred for logging servo values in intraframes, since these
146 typically lie close to the midpoint of 1500us.
148 #### Predict vbatref (9)
149 This predictor is set to the "vbatref" field written in the log header. It is used when logging intraframe battery
150 voltages in Cleanflight, since these are expected to be broadly similar to the first battery voltage seen during
153 #### Predict last main-frame time (10)
154 This predictor is set to the last logged `time` field from the main frame. This is used when predicting timestamps of
155 non-main frames (e.g. that might be logging the timing of an event that happened during the main loop cycle, like a GPS
159 The field encoder's job is to use fewer bits to represent values which are closer to zero than for values that are
160 further from zero. Blackbox supports a range of different encoders, which should be chosen on a per-field basis in order
161 to minimize the encoded data size. The choice of best encoder is based on the probability distribution of the values
162 which are to be encoded. For example, if a field is almost always zero, then an encoding should be chosen for it which
163 can encode that case into a very small number of bits, such as one. Conversely, if a field is normally 8-16 bits large,
164 it would be wasteful to use an encoder which provided a special short encoded representation for zero values, because
165 this will increase the encoded length of larger values.
167 These encoders are presently available:
169 #### Unsigned variable byte (1)
170 This is the most straightforward encoding. This encoding uses the lower 7 bits of an encoded byte to store the lower 7
171 bits of the field's value. The high bit of that encoded byte is set to one if more than 7 bits are required to store the
172 value. If the value did exceed 7 bits, the lower 7 bits of the value (which were written to the log) are removed from
173 the value (by right shift), and the encoding process begins again with the new value.
175 This can be represented by the following algorithm:
178 while (value > 127) {
179 writeByte((uint8_t) (value | 0x80)); // Set the high bit to mean "more bytes follow"
185 Here are some example values encoded using variable-byte encoding:
187 | Input value | Output encoding |
188 | ----------- | --------------- |
194 | 23456 | 0xA0 0xB7 0x01 |
196 #### Signed variable byte (0)
197 This encoding applies a pre-processing step to fold negative values into positive ones, then the resulting unsigned
198 number is encoded using unsigned variable byte encoding. The folding is accomplished by "ZigZag" encoding, which is
202 unsigned32 = (signed32 << 1) ^ (signed32 >> 31)
205 ZigZag encoding is preferred against simply casting the signed integer to unsigned, because casting would cause small
206 negative quantities to appear to be very large unsigned integers, causing the encoded length to be similarly large.
207 ZigZag encoding ensures that values near zero are still near zero after encoding.
209 Here are some example integers encoded using ZigZag encoding:
211 | Input value | ZigZag encoding |
212 | ----------- | --------------- |
217 | 2147483647 | 4294967294 |
218 | -2147483648 | 4294967295 |
221 The value is negated, treated as an unsigned 14 bit integer, then encoded using unsigned variable byte encoding. This
222 bizarre encoding is used in Cleanflight for battery pack voltages. This is because battery voltages are measured using a
223 14-bit ADC, with a predictor which is set to the battery voltage during arming, which is expected to be higher than any
224 voltage experienced during flight. After the predictor is subtracted, the battery voltage will almost certainly be below
227 This results in small encoded values when the voltage is closely below the initial one, at the expense of very large
228 encoded values if the voltage rises higher than the initial one.
230 #### Elias delta unsigned 32-bit (4)
231 Because this encoding produces a bitstream, this is the only encoding for which the encoded value might not be a whole
232 number of bytes. If the bitstream isn't aligned on a byte boundary by the time the next non-Elias Delta field arrives,
233 or the end of the frame is reached, the final byte is padded with zeros byte-align the stream. This encoding requires
234 more CPU time than the other encodings because of the bit juggling involved in writing the bitstream.
236 When this encoder is chosen to encode all of the values in Cleanflight interframes, it saves about 10% bandwidth
237 compared to using a mixture of the other encodings, but uses too much CPU time to be practical.
239 [The basic encoding algorithm is defined on Wikipedia](https://en.wikipedia.org/wiki/Elias_delta_coding). Given these
243 /* Write `bitCount` bits from the least-significant end of the `bits` integer to the bitstream. The most-significant bit
244 * will be written first
246 void writeBits(uint32_t bits, unsigned int bitCount);
248 /* Returns the number of bits needed to hold the top-most 1-bit of the integer 'i'. 'i' must not be zero. */
249 unsigned int numBitsToStoreInteger(uint32_t i);
252 This is our reference implementation of Elias Delta:
255 // Value must be more than zero
256 void writeU32EliasDeltaInternal(uint32_t value)
258 unsigned int valueLen, lengthOfValueLen;
260 valueLen = numBitsToStoreInteger(value);
261 lengthOfValueLen = numBitsToStoreInteger(valueLen);
263 // Use unary to encode the number of bits we'll need to write the length of the value
264 writeBits(0, lengthOfValueLen - 1);
266 // Now write the length of the value
267 writeBits(valueLen, lengthOfValueLen);
269 // Having now encoded the position of the top bit of value, write its remaining bits
270 writeBits(value, valueLen - 1);
274 To this we add a wrapper which allows encoding both the value zero and MAXINT:
277 void writeU32EliasDelta(uint32_t value)
279 /* We can't encode value==0, so we need to add 1 to the value before encoding
281 * That would make it impossible to encode MAXINT, so use 0xFFFFFFFF as an escape
282 * code with an additional bit to choose between MAXINT-1 or MAXINT.
284 if (value >= 0xFFFFFFFE) {
285 // Write the escape code
286 writeU32EliasDeltaInternal(0xFFFFFFFF);
287 // Add a one bit after the escape code if we wanted "MAXINT", or a zero if we wanted "MAXINT - 1"
288 writeBits(value - 0xFFFFFFFE, 1);
290 writeU32EliasDeltaInternal(value + 1);
295 Here are some reference encoded bit patterns produced by writeU32EliasDelta:
297 | Input value | Encoded bit string |
298 | ----------- | ------------------ |
315 | 225 | 00010001100010 |
316 | 4294967292 | 000001000001111111111111111111111111111101 |
317 | 4294967293 | 000001000001111111111111111111111111111110 |
318 | 4294967294 | 0000010000011111111111111111111111111111110 |
319 | 4294967295 | 0000010000011111111111111111111111111111111 |
321 Note that the very common value of zero encodes to a single bit, medium-sized values like 225 encode to 14 bits (an
322 overhead of 4 bits over writing a plain 8 bit value), and typical 32-bit values like 4294967293 encode into 42 bits, an
325 #### Elias delta signed 32-bit (5)
326 The value is first converted to unsigned using ZigZag encoding, then unsigned Elias-delta encoding is applied.
329 First, an 8-bit (one byte) header is written. This header has its bits set to zero when the corresponding field (from a
330 maximum of 8 fields) is set to zero, otherwise the bit is set to one. The least-signficant bit in the header corresponds
331 to the first field to be written. This header is followed by the values of only the fields which are non-zero, written
332 using signed variable byte encoding.
334 This encoding is preferred for groups of fields in interframes which are infrequently updated by the flight controller.
335 This will mean that their predictions are usually perfect, and so the value to be encoded for each field will normally
336 be zero. This is common for values like RC inputs and barometer readings, which are updated in only a fraction of main
339 For example, given these field values to encode:
345 This would be encoded:
348 0b00010100, 0x04, 0x08
352 A 2-bit header is written, followed by 3 signed field values of up to 32 bits each. The header value is based on the
353 maximum size in bits of the three values to be encoded as follows:
355 | Header value | Maximum field value size | Field range |
356 | ------------ | ------------------------ | -------------------------- |
357 | 0 | 2 bits | [-2...1] |
358 | 1 | 4 bits | [-8...7] |
359 | 2 | 6 bits | [-32...31] |
360 | 3 | Up to 32 bits | [-2147483648...2147483647] |
362 If any of the three values requires more than 6 bits to encode, a second, 6-bit header value is written in the lower
363 bits of the initial header byte. This second header has 2 bits for each of the encoded values which represents how many
364 bytes are required to encode that value. The least-significant bits of the header represent the first field which is
365 encoded. The values for each possibility are as follows:
367 | Header value | Field size | Field range |
368 | ------------ | ---------- | -------------------------- |
369 | 0 | 1 byte | [-127...128] |
370 | 1 | 2 bytes | [-32768...32767] |
371 | 2 | 3 bytes | [-8388608...8388607] |
372 | 3 | 4 bytes | [-2147483648...2147483647] |
374 This header is followed by the actual field values in order, written least-significant byte first, using the byte
375 lengths specified in the header.
377 So bringing it all together, these encoded bit patterns are possible, where "0" and "1" mean bits fixed to be those
378 values, "A", "B", and "C" represent the first, second and third fields, and "s" represents the bits of the secondary
379 header in the case that any field is larger than 6 bits:
384 10AA AAAA 00BB BBBB 00CC CCCC
385 11ss ssss (followed by fields of byte lengths specified in the "s" header)
388 This encoding is useful for fields like 3-axis gyroscopes, which are frequently small and typically have similar
392 An 8-bit header is written, followed by 4 signed field values of up to 16 bits each. The 8-bit header value has 2 bits
393 for each of the encoded fields (the least-significant bits represent the first field) which represent the
394 number of bits required to encode that field as follows:
396 | Header value | Field value size | Field range |
397 | ------------ | ---------------- | ---------------- |
398 | 0 | 0 bits | [0...0] |
399 | 1 | 4 bits | [-8...7] |
400 | 2 | 8 bits | [-128...127] |
401 | 3 | 16 bits | [-32768...32767] |
403 This header is followed by the actual field values in order, written as if the output stream was a bit-stream, with the
404 most-significant bit of the first field ending up in the most-significant bits of the first written byte. If the number
405 of nibbles written is odd, the final byte has its least-significant nibble set to zero.
407 For example, given these field values:
413 Choosing from the allowable field value sizes, they may be encoded using this many bits each:
419 The corresponding header values for these lengths would be:
425 So the header and fields would be encoded together as:
428 0b01010010, 0x0D, 0x42
432 This encoding does not write any bytes to the file. It is used when the predictor will always perfectly predict the
433 value of the field, so the remainder is always zero. In practice this is only used for the "loopIteration" field in
434 interframes, which is always perfectly predictable based on the logged frame's position in the sequence of frames and
435 the "P interval" setting from the header.
437 ## Log file structure
438 A logging session begins with a log start marker, then a header section which describes the format of the log, then the
439 log payload data, and finally an optional "log end" event ("E" frame).
441 A single log file can be comprised of one or more logging sessions. Each session may be preceded and followed by any
442 amount of non-Blackbox data. This data is ignored by the Blackbox log decoding tools. This allows for the logging device
443 to be alternately used by the Blackbox and some other system (such as MSP) without requiring the ability to begin a
444 separate log file for each separate activity.
447 The log start marker is "H Product:Blackbox flight data recorder by Nicholas Sherlock\n". This marker is
448 used to discover the beginning of the flight log if the log begins partway through a file. Because it is such a long
449 string, it is not expected to occur by accident in any sequence of random bytes from other log device users.
452 The header is comprised of a sequence of lines of plain ASCII text. Each header line has the format `H fieldname:value`
453 and ends with a '\n'. The overall header does not have a terminator to separate it from the log payload
454 (the header implicitly ends when a line does not begin with an 'H' character).
456 The header can contain some of these fields:
458 #### Data version (required)
459 When the interpretation of the Blackbox header changes due to Blackbox specification updates, the log version is
460 incremented to allow backwards-compatibility in the decoder:
466 #### Logging interval
467 Not every main loop iteration needs to result in a Blackbox logging iteration. When a loop iteration is not logged,
468 Blackbox is not called, no state is read from the flight controller, and nothing is written to the log. Two header lines
469 are included to note which main loop iterations will be logged:
472 This header notes which of the main loop iterations will record an "I" intraframe to the log. If main loop iterations
473 with indexes divisible by 32 will be logged as "I" frames, the header will be:
479 The first main loop iteration seen by Blackbox will be numbered with index 0, so the first main loop iteration will
480 always be logged as an intraframe.
483 Not every "P" interframe needs to be logged. Blackbox will log a portion of iterations in order to bring the total
484 portion of logged main frames to a user-chosen fraction. This fraction is called the logging rate. The smallest possible
485 logging rate is `(1/I interval)` which corresponds to logging only "I" frames at the "I" interval and discarding all
486 other loop iterations. The maximum logging rate is `1/1`, where every main loop iteration that is not an "I" frame is
487 logged as a "P" frame. The header records the logging rate fraction in `numerator/denominator` format like so:
493 The logging fraction given by `num/denom` should be simplified (i.e. rather than 2/6, a logging rate of 1/3 should
496 Given a logging rate of `num/denom` and an I-frame interval of `I_INTERVAL`, the frame type to log for an iteration
497 of index `iteration` is given by:
500 if (iteration % I_INTERVAL == 0)
503 if ((iteration % I_INTERVAL + num - 1) % denom < num)
506 return '.'; // i.e. don't log this iteration
509 For an I-interval of 32, these are the resulting logging patterns at some different P logging rates.
511 | Logging rate | Main frame pattern | Actual portion logged |
512 | ------------ | ----------------------------------------------------------------- | --------------------- |
513 | 1 / 32 | I...............................I...............................I | 0.03 |
514 | 1 / 6 | I.....P.....P.....P.....P.....P.I.....P.....P.....P.....P.....P.I | 0.19 |
515 | 1 / 3 | I..P..P..P..P..P..P..P..P..P..P.I..P..P..P..P..P..P..P..P..P..P.I | 0.34 |
516 | 1 / 2 | I.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.I.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.I | 0.50 |
517 | 2 / 3 | I.PP.PP.PP.PP.PP.PP.PP.PP.PP.PP.I.PP.PP.PP.PP.PP.PP.PP.PP.PP.PP.I | 0.66 |
518 | 5 / 6 | I.PPPPP.PPPPP.PPPPP.PPPPP.PPPPP.I.PPPPP.PPPPP.PPPPP.PPPPP.PPPPP.I | 0.81 |
519 | 1 / 1 | IPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPIPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPI | 1.00 |
522 #### Firmware type (optional)
523 Because Blackbox records the internal flight controller state, the interpretation of the logged data will depend
524 on knowing which flight controller recorded it. To accomodate this, the name of the flight controller should be recorded:
527 H Firmware type:Cleanflight
530 More details should be included to help narrow down the precise flight-controller version (but these are not required):
533 H Firmware revision:c49bd40
534 H Firmware date:Aug 28 2015 16:49:11
537 #### Field X name (required)
538 This header is a comma-separated list of the names for the fields in the 'X' frame type:
541 H Field I name:loopIteration,time,axisP[0],axisP[1]...
544 The decoder assumes that the fields in the 'P' frame type will all have the same names as those in the 'I' frame, so
545 a "Field P name" header does not need to be supplied.
547 #### Field X signed (optional)
548 This is a comma-separated list of integers which are set to '1' when their corresponding field's value should be
549 interpreted as signed after decoding, or '0' otherwise:
552 H Field I signed:0,0,1,1...
555 #### Field X predictor (required)
556 This is a comma-separated list of integers which specify the predictors for each field in the specified frame type:
559 H Field I predictor:0,0,0,0...
562 #### Field X encoding (required)
563 This is a comma-separated list of integers which specify the encoding used for each field in the specified frame type:
566 H Field X encoding:1,1,0,0...
570 This header provides the reference voltage that will be used by predictor #9.
573 This header provides the minimum value sent by Cleanflight to the ESCs when armed, it is used by predictor #4.
575 #### Additional headers
576 The decoder ignores headers that it does not understand, so you can freely add any headers that you require in order to
577 properly interpret the meaning of the logged values.
579 For example, to create a graphical displays of RC sticks and motor percentages, the Blackbox rendering tool requires
580 the additional headers "rcRate" and "maxthrottle". In order to convert raw gyroscope, accelerometer and voltage readings
581 into real-world units, the Blackbox decoder requires the calibration constants "gyro.scale", "acc_1G" and "vbatscale".
582 These headers might look like:
587 H gyro.scale:0x3d79c190
594 The log payload is a concatenated sequence of logged frames. Each frame type which is present in the log payload must
595 have been previously described in the log header (with Frame X name, etc. headers). Each frame begins with a single
596 capital letter to specify the type of frame (I, P, etc), which is immediately followed by the frame's field data. There
597 is no frame length field, checksum, or trailer.
599 The field data is encoded by taking an array of raw field data, computing the predictor for each field, subtrating this
600 predictor from the field, then applying the field encoders to each field in sequence to serialize them to the log.
602 For example, imagine that we are encoding three fields in an intraframe, are using zero-predictors for each field (#0),
603 and are encoding the values using the unsigned variable byte encoding (#1). For these field values:
609 We would encode a frame:
612 'I', 0x01, 0x02, 0x03
615 Imagine that we are encoding an array of motor commands in an interframe. We will use the previous motor commands as a
616 predictor, and encode the resulting values using signed variable byte encoding. The motor command values seen in the
617 previous logged iteration were:
620 1430, 1500, 1470, 1490
623 And the motor commands to be logged in this iteration are:
626 1635, 1501, 1469, 1532
629 After subtracting the predictors for each field, we will be left with:
635 We will apply ZigZag encoding to each field, which will give us:
641 We will use unsigned variable byte encoding to write the resulting values to the log, which will give us:
644 'P', 0x9A, 0x03, 0x02, 0x01, 0x54
648 The log end marker is an optional Event ("E") frame of type 0xFF whose payload is the string "End of log\0". The
649 payload ensures that random data does not look like an end-of-log marker by chance. This event signals the tidy ending
650 of the log. All following bytes until the next log-begin marker (or end of file) should be ignored by the log
654 'E', 0xFF, "End of log
\0", 0x00
658 Any damage experienced to the log during recording is overwhelmingly due to subsequences of bytes being dropped by the
659 logging device due to overflowing buffers. Accordingly, Blackbox logs do not bother to include any checksums (bytes are
660 not expected to be damaged by the logging device without changing the length of the message). Because of the tight
661 bandwidth requirements of logging, neither a frame length field nor frame trailer is recorded that would allow for the
662 detection of missing bytes.
664 Instead, the decoder uses a heuristic in order to detect damaged frames. The decoder reads an entire frame from the log
665 (using the decoder for each field which is the counterpart of the encoder specified in the header), then it checks to
666 see if the byte immediately following the frame, which should be the beginning of a next frame, is a recognized
667 frame-type byte (e.g. 'I', 'P', 'E', etc). If that following byte represents a valid frame type, it is assumed that the
668 decoded frame was the correct length (so was unlikely to have had random ranges of bytes removed from it, which would
669 have likely altered the frame length). Otherwise, the frame is rejected, and a valid frame-type byte is looked for
670 immediately after the frame-start byte of the frame that was rejected. A rejected frame causes all subsequent
671 interframes to be rejected as well, up until the next intraframe.
673 A frame is also rejected if the "loopIteration" or "time" fields have made unreasonable leaps forward, or moved at
674 all backwards. This suffices to detect almost all log corruption.